
package com.github.tzraeq.d2d;

import android.content.Context;
import android.graphics.Bitmap;
import android.media.AudioFormat;
import android.media.AudioRecord;
import android.media.MediaRecorder;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.util.Base64;
import android.util.SparseArray;

import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableMap;
import com.github.tzraeq.d2d.listener.D2dTransListener;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import cn.anxin.a9.avplayer.AVCamera;
import cn.anxin.a9.libice.LibIce;

public class RND2dModule extends ReactContextBaseJavaModule {

    private final static String SDP_NEWLINE = "\n";
    private final static String SDP_CANDIDATE_PREFIX = "a=candidate:";

    public final static String RND2D_ON_COMPLETE = "d2dOnComplete";
    public final static String RND2D_ON_DATA = "d2dOnData";

    private final ReactApplicationContext reactContext;

    private String serverAddr;

    private int sampleRateInHz;
    private int channelConfig;
    private int audioFormat;
    private AudioRecord recorder;
    private int bufferSize;
    private boolean isRecording;

    private SparseArray<LibIce> libIces = new SparseArray<LibIce>();
    private RND2dPlayerManager rnd2dPlayerManager;

    // private static LibIce libIce;

    public RND2dModule(ReactApplicationContext reactContext, RND2dPlayerManager rnd2dPlayerManager) {
        super(reactContext);
        this.reactContext = reactContext;
        this.rnd2dPlayerManager = rnd2dPlayerManager;
        // libIce = new LibIce();
        // libIce = LibIce.getInstance();
        // libIce.removeAllListeners();
        // D2dTransListener listener = new D2dTransListener(reactContext);
        // libIce.addListener(listener);
    }

    @Override
    public String getName() {
        return "RND2d";
    }

    @Override
    public Map<String, Object> getConstants() {
        Map<String,Object> constants = new HashMap<>();
        constants.put("D2D_ON_COMPLETE",RND2D_ON_COMPLETE);
        constants.put("D2D_ON_DATA",RND2D_ON_DATA);

        return constants;
    }

    @ReactMethod
    public void init(final Integer id, Promise promise) {
        LibIce libIce = new LibIce();
        libIces.put(id, libIce);
        D2dTransListener listener = new D2dTransListener(reactContext, id);
        libIce.addListener(listener);
        promise.resolve(null);
    }

    @ReactMethod
    public void create(final Integer id, String addr, String username, String password, Promise promise) {
        serverAddr = addr;
        int ret = findIce(id).create(addr,addr,username,password);
        promise.resolve(ret);
    }

    @ReactMethod
    public void getSdp(final Integer id, Promise promise) {
        String sdp = findIce(id).getSdp();
        if(null == serverAddr){
            sdp = resolveSdp(sdp);
        }
        promise.resolve(sdp);
    }

    @ReactMethod
    public void getSdpBase64(final Integer id, Promise promise) {
        String sdp = findIce(id).getSdp();
        if(null == serverAddr){
            sdp = resolveSdp(sdp);
        }
        promise.resolve(Base64.encodeToString(sdp.getBytes(),Base64.NO_WRAP));
    }

    @ReactMethod
    public void start(final Integer id, String sdp, String client, Promise promise) {
        int ret = findIce(id).start(sdp,client);
        if(0 == ret){
            promise.resolve(ret);
        }else{
            promise.reject(ret+"","");
        }
    }

    @ReactMethod
    public void startBase64(final Integer id, String sdp, String client, Promise promise) {
        String remoteSdp = new String(Base64.decode(sdp,Base64.NO_WRAP));
        int ret = findIce(id).start(remoteSdp,client);
        if(0 == ret){
            promise.resolve(ret);
        }else{
            promise.reject(ret+"","");
        }
    }

    @ReactMethod
    public void initAudio(final Integer id, ReadableMap options,Promise promise) {
        sampleRateInHz = 44100;
        if (options.hasKey("sampleRate")) {
            sampleRateInHz = options.getInt("sampleRate");
        }

        channelConfig = AudioFormat.CHANNEL_IN_MONO;
        if (options.hasKey("channels")) {
            if (options.getInt("channels") == 2) {
                channelConfig = AudioFormat.CHANNEL_IN_STEREO;
            }
        }

        audioFormat = AudioFormat.ENCODING_PCM_16BIT;
        if (options.hasKey("bitsPerSample")) {
            if (options.getInt("bitsPerSample") == 8) {
                audioFormat = AudioFormat.ENCODING_PCM_8BIT;
            }
        }

        isRecording = false;

        bufferSize = AudioRecord.getMinBufferSize(sampleRateInHz, channelConfig, audioFormat);
        int recordingBufferSize = bufferSize * 3;
        recorder = new AudioRecord(MediaRecorder.AudioSource.VOICE_COMMUNICATION, sampleRateInHz, channelConfig, audioFormat, recordingBufferSize);
        promise.resolve(null);
    }

    @ReactMethod
    public void startAudio(final Integer id, Promise promise){
        isRecording = true;
        recorder.startRecording();

        new Thread(new Runnable() {
            public void run() {
                try {
                    switch (audioFormat) {
                        case AudioFormat.ENCODING_PCM_8BIT: // 8-bit = 1 byte per sample
                        {
                            byte[] audiodata = new byte[bufferSize];
                            while (isRecording) {
                                int rt = recorder.read(audiodata, 0, bufferSize);
                                if (AudioRecord.ERROR_INVALID_OPERATION != rt) {
                                    findIce(id).sendPCM(audiodata, rt);
                                }
                            }
                        }
                        break;
                        case AudioFormat.ENCODING_PCM_16BIT: // 16-bit = 2 bytes per sample
                        {
                            short[] audiodata = new short[bufferSize];
                            while (isRecording) {
                                int rt = recorder.read(audiodata, 0, bufferSize);
                                if (AudioRecord.ERROR_INVALID_OPERATION != rt){
                                    findIce(id).sendPCM(audiodata,rt);
                                }
                            }
                        }
                        break;
                    }

                    recorder.stop();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }).start();
        promise.resolve(null);
    }

    @ReactMethod
    public void stopAudio(final Integer id, Promise promise) {
        isRecording = false;
        promise.resolve(null);
    }

    @ReactMethod
    public void destroy(final Integer id, Promise promise) {
        findIce(id).destroy();
        promise.resolve(null);
    }

    @ReactMethod
    public void setTimeout(final Integer id, final Integer timeout, Promise promise) {
        findIce(id).setTimeout(timeout);
        promise.resolve(null);
    }

    @ReactMethod
    public void setPeerSourceReportInterval(final Integer id, final Integer interval, Promise promise) {
        findIce(id).setPeerSourceReportInterval(interval);
        promise.resolve(null);
    }

    @ReactMethod
    public void bindPlayer(final Integer id, Promise promise) {
        findIce(id).addTarget(this.rnd2dPlayerManager.getCamera());
    }

    @ReactMethod
    public void play(final Integer id, Promise promise) {
        AVCamera camera = this.rnd2dPlayerManager.getCamera();
        camera.play();
        promise.resolve(null);
    }

    @ReactMethod
    public void stop(final Integer id, Promise promise) {
        AVCamera camera = this.rnd2dPlayerManager.getCamera();
        camera.stop();
        promise.resolve(null);
    }

    @ReactMethod
    public void mute(final Integer id, Boolean muted, Promise promise) {
        AVCamera camera = this.rnd2dPlayerManager.getCamera();
        camera.mute(muted.booleanValue());
        promise.resolve(null);
    }

    @ReactMethod
    public void takeSnapshot(final Integer id, String path, Promise promise) {
        AVCamera camera = this.rnd2dPlayerManager.getCamera();
        Bitmap bmap = camera.getSnapshot();
        if(null == bmap){
            promise.resolve(false);
        }else{
            File f = new File(path);
            try {
                if(!f.exists()){
                    f.createNewFile();
                }

                FileOutputStream fOut = new FileOutputStream(f);
                bmap.compress(Bitmap.CompressFormat.JPEG, 80, fOut);
                fOut.close();
                promise.resolve(true);
            } catch (IOException e) {
                e.printStackTrace();
                promise.reject(e);
            }
        }
    }

    @ReactMethod
    public void setDropFrame(final Integer id, Boolean dropFrame, Promise promise) {
        findIce(id).setDropFrame(dropFrame?1:0);
        promise.resolve(null);
    }

    @ReactMethod
    public void setScalable(final Integer id, Boolean scaleable, Promise promise) {
        AVCamera camera = this.rnd2dPlayerManager.getCamera();
        camera.setScalable(scaleable);
    }

    @ReactMethod
    public void setMaxScaleLevel(final Integer id, double level, Promise promise) {
        AVCamera camera = this.rnd2dPlayerManager.getCamera();
        float l = (float)level;
        camera.setMaxScaleLevel(l);
    }

    private String resolveSdp(String sdp){
        String[] lines = sdp.split(SDP_NEWLINE);
        List<String> list = new ArrayList<String>(lines.length);
        String wifiIP = getWifiIPAddress();
        for (String token :lines) {
            if(token.startsWith(SDP_CANDIDATE_PREFIX) && !token.contains(wifiIP)){
                continue;
            }else{
                list.add(token);
            }
        }
        StringBuffer stringBuffer = new StringBuffer();
        for (String token: list) {
            stringBuffer.append(token).append(SDP_NEWLINE);
        }
        String s = stringBuffer.deleteCharAt(stringBuffer.length() - 1).toString();
        return s;
    }

    private String getWifiIPAddress(){
        WifiManager wifi = (WifiManager)reactContext.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        if(!wifi.isWifiEnabled()){
            return null;
        }
        WifiInfo info = wifi.getConnectionInfo();
        String stringip = longToIP(info.getIpAddress());
        return stringip;
    }

    public static String longToIP(int longIp){
        StringBuffer sb = new StringBuffer("");
        String[] strip=new String[4];
        strip[3]=String.valueOf((longIp >>> 24));
        strip[2]=String.valueOf((longIp & 0x00FFFFFF) >>> 16);
        strip[1]=String.valueOf((longIp & 0x0000FFFF) >>> 8);
        strip[0]=String.valueOf((longIp & 0x000000FF));
        sb.append(strip[0]);
        sb.append(".");
        sb.append(strip[1]);
        sb.append(".");
        sb.append(strip[2]);
        sb.append(".");
        sb.append(strip[3]);
        return sb.toString();
    }

    private LibIce findIce(final Integer id) {
        LibIce libIce = libIces.get(id);
        return libIce;
    }
}